{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PackageImports #-}
{-# LANGUAGE ParallelListComp #-}
{-# LANGUAGE RebindableSyntax #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE NoImplicitPrelude #-}

-- | Primitive Turtle commands for doing Turtle graphics. The commands
-- follow the original LOGO naming convention, but they integrate
-- seamlessly with the rest of CodeWorld.
module Extras.Turtle
  ( -- $intro
    Turtle,
    TurtleCommand,
    TurtleProgram,

    -- * Standard Turtle commands
    fd,
    bk,
    rt,
    lt,
    seth,
    setxy,
    home,
    pu,
    pd,

    -- * Specific Turtle commands (only in CodeWorld)
    overxy,
    sethome,
    origin,
    turtle,
    randomized,
    repeatRandom,

    -- * Program control (re-exported from "Extras.Util")
    repeat,
    run,
    foreach,
    forloop,

    -- * Tracks
    Track,
    track,
    tracks,
    partialTracks,
    randomTracks,
    trackLength,
    alongTrack,

    -- * Drawing extensions
    polylines,
    thickPolylines,
    solidPolygons,
    dottylines,
    dottyline,

    -- * Examples
    turtleExamples,

    -- * Custom Turtles
    customTurtle,
    turtlePosition,
    turtleAngle,
  )
where

import Extras.Cw (randomDrawingOf, slideshow)
import Extras.Op
import Extras.Util
import Prelude

-------------------------------------------------------------------------------

-- $intro
-- = Turtle API
--
-- To use the extra features in this module, you must begin your code with this
-- line:
--
-- > import Extras.Turtle
--
-- This module allows you to use 'Turtle' programs to create
-- Turtle graphics.
-- For general information about Turtle graphics, you can consult
-- the Wikipedia page at <https://en.wikipedia.org/wiki/Turtle_graphics>
--
-- This module defines primitive Turtle commands to move and turn
-- a Turtle. Two functions, 'run' and 'repeat', can be used
-- to create new Turtle commands out of lists of commands.
-- The primitive Turtle commands in this module follow the command names,
-- syntax and semantics of the original LOGO commands as much as possible,
-- but this module is not a standalone LOGO interpreter.
-- Instead, Turtle programs in this module can be
-- used to create Tracks, which are lists of Points. You can then
-- feed those Tracks into regular CodeWorld graphical primitives,
-- such as 'polyline', 'curve' or 'polygon'. You can also use
-- the CodeWorld language to process the list of Points further
-- or to combine the shapes generated by a Turtle program with
-- shapes generated in other ways.
--
-- Example:
--
-- > program = drawingOf(polyline(track(turtleProgram)))
-- >
-- > turtleProgram = repeat(4, [fd(1), rt(90) ])
--
-- The example above will show a square of side length 1 with
-- its lower left corner at the origin.

-------------------------------------------------------------------------------
-- Turtle
-------------------------------------------------------------------------------

-- | A Turtle data structure contains information about the current state
-- of a Turtle, such as its current position, its current heading and
-- whether its pen is up or down. You cannot manipulate the inner
-- workings of this structure. Instead, you use Turtle programs to
-- change the internal state of a Turtle.
--
-- A Turtle is initially positioned at the origin, with the pen
-- down and pointing upwards. You can use the command 'sethome'
-- to reset the initial position to a different point.
data Turtle = Turtle
  { position :: Point,
    heading :: Point,
    trace :: [Point],
    traces :: [[Point]],
    pen :: Pen,
    rndNumbers :: [Number]
  }

data Pen = Pu | Pd

homePosition = (0, 0)

homeHeading = (0, 1)

initialTurtle :: Turtle
initialTurtle =
  Turtle
    { position = homePosition,
      heading = homeHeading,
      trace = [homePosition],
      traces = [],
      pen = Pd,
      rndNumbers = []
    }

-- | A @customTurtle(x,y,angle)@ is a Turtle positioned at the point @(x,y)@,
-- which is pointing in the direction specified by the given @angle@,
-- where the angle is measured in
-- the normal CodeWorld way, i.e., 0 means pointing to the right,
-- and the angles increase counter-clockwise.
-- You can use this Turtle to have finer control over the Turtle.
--
-- Notes:
--
-- (1) Do not use the commands 'pu' and 'pd' on custom turtles, because
-- they do not have any visible effect. You must generate your own tracks
-- explicitly with the help of the 'turtlePosition' function.
-- (2) Do not use 'randomized' either,
-- because a custom turtle does not have access to random numbers.
-- (3) Probably, the only Turtle commands that are useful with custom
-- turtles are 'fd', 'bk', 'rt' and 'lt'. Any other handling of custom turtles
-- should be done with regular CodeWorld functions.
--
-- Example:
--
-- > program = randomDrawingOf(draw)
-- >   where
-- >   draw(random) = colored(thickPolyline(turtleTrack,0.2),red)
-- >     where
-- >     turtleTrack = forloop(input,cond,next,output)
-- >     input = (customTurtle(-10,-10,0), random)
-- >     cond(t,_) = x < 12 && y < 12 where (x,y) = turtlePosition(t)
-- >     next(t,r) = ( run([mayTurn(r#1), fd(2*r#2)])(t), rest(r,2) )
-- >     output(t,_) = turtlePosition(t)
-- >     mayTurn(r) = if r < 0.5 then turned else itself
-- >     turned(t) = if turtleAngle(t) < 45 then lt(90)(t) else rt(90)(t)
-- >
--
-- The example above randomly moves a custom turtle either up or to the right.
-- The turtle starts at the lower left corner of the output,
-- and it moves until it reaches either the top or the
-- right side of the output, whichever is reached first. This example
-- uses 'randomDrawingOf' from "Extras.Cw" and 'itself' from "Extras.Util".
customTurtle :: (Number, Number, Number) -> Turtle
customTurtle (x, y, angle) =
  initialTurtle
    { position = (x, y),
      heading = rotatedPoint ((1, 0), angle),
      trace = [(x, y)],
      pen = Pu
    }

-- | The current position of the given Turtle.
turtlePosition :: Turtle -> Point
turtlePosition = position

-- | The orientation of the given Turtle measured in the normal way
-- that CodeWorld uses angles, so that 0 means pointing to
-- the right, and angles increase counter-clockwise.
turtleAngle :: Turtle -> Number
turtleAngle (turtle) = vectorDirection (turtle .# heading)

-------------------------------------------------------------------------------
-- API
-------------------------------------------------------------------------------

-- | A Turtle command is a function that modifies the state of a Turtle.
type TurtleCommand = Turtle -> Turtle

-- | A Turtle program is a sequence of Turtle commands.
-- You can use the function
-- 'run' to convert a Turtle program into a single Turtle command that will
-- work the same way as the built-in Turtle commands. This type alias is not
-- used in this module, but you can use it in your code to specify the type of
-- a Turtle program. The examples in the documentation of 'sethome' and
-- 'overxy' show how you can use it.
type TurtleProgram = [TurtleCommand]

-- | A Track is a list of Points.
-- Tracks are generated by Turtle commands.
type Track = [Point]

-- | Convert a Turtle command into a Track. This is a simplified version
-- of 'tracks' that can be used when you know for sure that your Turtle
-- command will create only one Track. When that is not the case, the
-- different tracks will be joined together into a single Track, as
-- if the Turtle pen was always down.
track :: TurtleCommand -> Track
track (cmd) = cmd .# tracks .# concatenation

-- | Convert a Turtle command into a list of Tracks. A Track ends when
-- you use the 'pu' Turtle command. After that, the Turtle position
-- is tracked internally, but no points are added to the Track. A
-- new Track can be started by using the 'pd' command.
--
-- Example:
--
-- > program = drawingOf(pictures(fig) & solidRectangle(20,20))
-- >   where
-- >   fig = [ colored(thickPolyline(t,0.03),c)
-- >         | t <- tracks(turtleProgram)
-- >         | c <- repeating([red, purple, blue, green, orange, yellow])
-- >         ]
-- >
-- >   turtleProgram = run(foreach([1..360], singleLine))
-- >
-- >   singleLine(x) = run([pd, fd(x/100), lt(59), pu])
tracks :: TurtleCommand -> [Track]
tracks (cmd) = initialTurtle .# cmd .# saveTrace .# reversed

{- The example above in Python:

# Python program to draw
# Rainbow Benzene
# using Turtle Programming
import turtle
colors = ['red', 'purple', 'blue', 'green', 'orange', 'yellow']
t = turtle.Pen()
turtle.bgcolor('black')
for x in range(360):
    t.pencolor(colors[x%6])
    t.width(x/100 + 1)
    t.forward(x)
    t.left(59)

-}

-- | A list of all the partial tracks corresponding to the given command.
-- Useful to observe a step-by-step construction of the tracks.
--
-- Example:
--
-- > import Extras.Turtle
-- > import Extras.Cw(slideshow)
-- >
-- > program = slideshow(foreach(slides,\s -> polylines(s)))
-- >
-- > slides = partialTracks(turtleProgram)
-- > turtleProgram = repeat(7, [square, rt(360/7)])
-- > square = repeat(4, [fd(4), rt(90)])
--
-- The example above creates a slide show that illustrates the
-- construction of seven squares. It uses the function 'slideshow'
-- from the module "Extras.Cw"
partialTracks :: TurtleCommand -> [[Track]]
partialTracks (turtleProg) = foreach ([2 .. sum (lengths)], partial)
  where
    fullTracks = tracks (turtleProg)
    fullLength = length (fullTracks)
    lengths = foreach (fullTracks, length)
    clengths = cumulativeSums (lengths)
    ctracks = zipped (clengths, fullTracks)
    partial (step)
      | n < fullLength = append (portion, taken)
      | otherwise = fullTracks
      where
        taken = selectedValues (ctracks, (<= step))
        n = length (taken)
        portion = first (fullTracks # (n + 1), step - (0 : clengths) # (n + 1))

-- | Use this function to run Turtle programs that can use random numbers.
-- You need to provide an infinite list of numbers, where each of them
-- is between 0 (included) and 1 (excluded).
-- A simple way to use random numbers is with the
-- function 'randomDrawingOf' from the module "Cw".
--
-- Example 1:
--
-- > import Extras.Turtle
-- > import Extras.Cw(randomDrawingOf)
-- >
-- > program = randomDrawingOf(draw)
-- >   where
-- >   draw(random) = polylines(randomTracks(random,turtleProgram))
-- >   turtleProgram = repeat(50, [repeatRandom(100, randomLine), rt(180)])
-- >   randomLine = [fd(10),bk(9.8),rt(2)]
--
-- Example 2:
--
-- > program = randomDrawingOf(draw)
-- >   where
-- >   draw(random) = colored(sun,yellow)
-- >     where
-- >     sun = solidCircle(2) & polylines(randomTracks(random,turtleProgram))
-- >   turtleProgram = repeat(500, [ pu, home, randomized(seth,360), fd(2)
-- >                               , pd, randomized(fd,4)
-- >                               ])
randomTracks :: ([Number], TurtleCommand) -> [Track]
randomTracks (randoms, cmd) = turtle .# cmd .# saveTrace .# reversed
  where
    turtle = initialTurtle {rndNumbers = randoms}

trackInfo :: Track -> [(Point, Vector)]
trackInfo (points) =
  [ (a, vectorDifference (b, a))
    | a <- points
    | b <- rest (points, 1)
  ]

-- | The length of the given Track
trackLength :: Track -> Number
trackLength (points) = sum (foreach (trackInfo (points), len))
  where
    len (_, dx) = vectorLength (dx)

-- | If @travel = alongTrack(points)@, then @travel@ is a function that can be
-- used to traverse a track traveling at 1 unit per second. The value
-- @travel(t)@ is a pair, where the first element is the location at time @t@
-- and the second element is the angle of the corresponding segment in
-- the given track.
--
-- Example:
--
-- > program = animationOf(movie)
-- >   where
-- >   movie(t) = placedAlong(turtleShape,travel(remainder(4*t,tlen)))
-- >            & polyline(turtleTrack)
-- >
-- >   placedAlong(pic,((x,y),a)) = translated(rotated(turtleShape,a),x,y)
-- >   travel = alongTrack(turtleTrack)
-- >   tlen = trackLength(turtleTrack)
-- >
-- >   greenTurtle = colored(turtleShape, green)
-- >   turtleShape = rotated(thickPolygon(track(turtle),0.1),-90)
-- >   turtleTrack = track(run(figs(11, poly(7,18/7))))
-- >   figs(n,fig) = [repeat(n,[run(fig),lt(360/n)])]
-- >   poly(n,len) = figs(n,[fd(len)])
alongTrack :: Track -> Number -> (Point, Number)
alongTrack (points)
  | empty (tinfo) = \t -> ((0, 0), 0)
  | otherwise = go
  where
    tinfo = trackInfo (points)
    tlens = foreach (tinfo, \(_, dx) -> vectorLength (dx))
    lerp (t, (ax, ay), (bx, by)) = (ax + t * (bx - ax), ay + t * (by - ay))
    go (t) =
      if t <= 0
        then let (x0, dx) = tinfo # 1 in (x0, vectorDirection (dx))
        else whileloop ((t, tlens, tinfo), cond, next, output)
    cond (t, ls, _) = nonEmpty (ls) && t >= ls # 1
    next (t, ls, xds) = (t - ls # 1, rest (ls, 1), rest (xds, 1))
    output (_, [], _) = go (0)
    output (t, ls, xds) = (lerp (t / ls # 1, x0, x1), angle)
      where
        (x0, dx) = xds # 1
        x1 = vectorSum (x0, dx)
        angle = vectorDirection (dx)

-------------------------------------------------------------------------------
-- Turtle Language
-------------------------------------------------------------------------------

-- | Move the Turtle forward by the given number of units
fd :: Number -> TurtleCommand
fd (len) (turtle) =
  turtle
    { position = position',
      trace = turtle .# addPoint (position')
    }
  where
    position' = (px + len * hx, py + len * hy)
    (px, py) = turtle .# position
    (hx, hy) = turtle .# heading

-- | Move the Turtle backward by the given number of units
bk :: Number -> TurtleCommand
bk (len) (turtle) =
  turtle
    { position = position',
      trace = turtle .# addPoint (position')
    }
  where
    position' = (px - len * hx, py - len * hy)
    (px, py) = turtle .# position
    (hx, hy) = turtle .# heading

-- | Turn the Turtle right (clockwise) by the given number of degrees
rt :: Number -> TurtleCommand
rt (angle) (turtle) =
  turtle
    { heading = rotatedPoint (turtle .# heading, - angle)
    }

-- | Turn the Turle left (counter-clockwise) by the given number of degrees
lt :: Number -> TurtleCommand
lt (angle) (turtle) =
  turtle
    { heading = rotatedPoint (turtle .# heading, angle)
    }

-- | Set the Turtle heading by first orienting it upright (pointing upwards)
--   and then rotating it clockwise by the given number of degrees
seth :: Number -> TurtleCommand
seth (angle) (turtle) =
  turtle
    { heading = rotatedPoint (homeHeading, - angle)
    }

-- | Move the Turtle to the absolute position given by the X and Y coordinates
setxy :: Point -> TurtleCommand
setxy (x, y) (turtle) =
  turtle
    { position = (x, y),
      trace = turtle .# addPoint (x, y)
    }

-- | Move the Turtle to the center and set the heading to upright
home :: TurtleCommand
home (turtle) =
  turtle
    { heading = homeHeading,
      position = homePosition,
      trace = turtle .# addPoint (homePosition)
    }

-- | Pen Up: Stop tracing the positions of the Turtle.
-- If the pen was down, this command will end the current
-- 'Track'. Otherwise, the command is ignored.
pu :: TurtleCommand
pu (turtle) = case turtle .# pen of
  Pu -> turtle
  Pd ->
    turtle
      { trace = [],
        traces = turtle .# saveTrace,
        pen = Pu
      }

-- | Pen Down: Start tracing the positions of the Turtle.
-- If the pen was up, this command will start a new 'Track'.
-- Otherwise, the command is ignored.
pd :: TurtleCommand
pd (turtle) = case turtle .# pen of
  Pd -> turtle
  Pu ->
    turtle
      { trace = [turtle .# position],
        pen = Pd
      }

-- Turtle Language Extensions

-- | Move the Turtle according to the given Point transformation
--
-- Example:
--
-- > turtleProgram :: TurtleProgram
-- > turtleProgram = [ pu, sethome(origin), fd(1), pd
-- >                 , repeat(6, [overxy(turn) ])
-- >                 , pu, fd(1), pd
-- >                 , turtle ]
-- >
-- > turn(point) = rotatedPoint(point, 360/6)
--
-- The example above constructs a hexagon without
-- altering the heading of the turtle. It
-- also illustrates how you can mix Turtle commands
-- with other CodeWorld functions, such as
-- 'rotatedPoint'.
overxy :: (Point -> Point) -> TurtleCommand
overxy (f) (turtle) =
  turtle
    { position = p,
      trace = turtle .# addPoint (p)
    }
  where
    p = f (turtle .# position)

-- | Set the starting point for the trace to the given coordinates.
-- Any Track recorded before using this command will be discarded.
-- This command is only useful for initializaing your Turtle
-- program, so that it starts at a different position.
--
-- You can also use this command to move the Turtle before starting
-- tracing your tracks. For example, to start your Turtle with
-- the pen up, you can use the following pattern:
--
-- > turtleProgram :: TurtleProgram
-- > turtleProgram = [pu,sethome(origin)] ++ otherCommands
sethome :: Point -> TurtleCommand
sethome (x, y) (turtle) =
  turtle
    { position = (x, y),
      trace = case turtle .# pen of
        Pu -> []
        Pd -> [(x, y)]
    }

-- | The origin is at the center of the output window
origin :: Point
origin = homePosition

-- | A simple drawing of a Turtle that can be used to observe
-- its current position and heading.
turtle :: TurtleCommand
turtle =
  run
    ( [ rt (150),
        fd (0.2),
        lt (120),
        fd (0.2),
        lt (60),
        fd (0.4),
        lt (120),
        fd (0.4),
        lt (60),
        fd (0.2),
        lt (120),
        fd (0.2),
        lt (30)
      ]
    )

-- | @randomized(cmd,maxnum)@ can be used to run one of the following
-- Turtle commands: 'fd', 'bk', 'rt', 'lt', 'seth', or with a custom command
-- that takes a Number as the argument, so that the command is run
-- with a random number between 0 (included) and maxnum (excluded).
-- If this function is used inside 'tracks' instead of inside 'randomTracks',
-- the value @maxnum/2@ will be passed to @cmd@ instead of a random number.
--
-- Example:
--
-- > program = randomDrawingOf(draw)
-- >   where
-- >   draw(random) = pictures([ colored(thickPolyline(t,0.25),c)
-- >                           | t <- randomTracks(random1,turtleProgram)
-- >                           | c <- random2
-- >                           ])
-- >         where
-- >         random1 = randomNumbers(random#1)
-- >         random2 = [ RGB(r,g,b)
-- >                   | [r,g,b] <- groups(randomNumbers(random#2),3)
-- >                   ]
-- >
-- >   turtleProgram = repeat(10, [pd,makeTrack,pu,home])
-- >   makeTrack = repeat(100, [ randomized(\r -> fd(2*r-1), 1.5)
-- >                           , randomized(\r -> rt(90*truncation(r)), 4)
-- >                           ])
randomized :: (Number -> TurtleCommand, Number) -> TurtleCommand
randomized (cmd, maxnum) (turtleOld) = cmd (num) (turtleNew)
  where
    turtleNew = turtleOld {rndNumbers = rest (turtleOld .# rndNumbers, 1)}
    num = maxnum * rnd
    rnd
      | empty (turtleOld .# rndNumbers) = 0.5
      | otherwise = turtleOld .# rndNumbers # 1

-- | Repeat a turtle Program a random number of times up to the given
-- maximum. This is a specialized version of 'randomized'.
repeatRandom :: (Number, TurtleProgram) -> TurtleCommand
repeatRandom (maxnum, prog) = randomized (\r -> repeat (r, prog), maxnum)

-- Drawing API Extensions

-- | Draw each Track in a list of tracks as a polyline.
polylines :: [Track] -> Picture
polylines (ls) = ls .$ polyline .# pictures

-- | Draw each Track in a list of tracks as a thick polyline
-- of the given thickness.
thickPolylines :: ([Track], Number) -> Picture
thickPolylines (ls, t) = pictures (foreach (ls, \l -> thickPolyline (l, t)))

-- | Draw each Track in a list of tracks as a solid polygon.
solidPolygons :: [Track] -> Picture
solidPolygons (ls) = ls .$ solidPolygon .# pictures

-- | Draw all the points in all the polylines of a list of tracks.
dottylines :: [Track] -> Picture
dottylines (ls) = ls .$ dottyline .# pictures

-- | Draw the vertices of a polyline as dots
dottyline :: [Point] -> Picture
dottyline (pts) = pts .$ makeDot .# pictures
  where
    makeDot (x, y) = translated (solidCircle (0.05), x, y)

-------------------------------------------------------------------------------
-- Turtle Aux
-------------------------------------------------------------------------------

addPoint :: Point -> Turtle -> [Point]
addPoint (p) (turtle) = case turtle .# pen of
  Pu -> turtle .# trace
  Pd -> p : turtle .# trace

saveTrace :: Turtle -> [[Point]]
saveTrace (turtle) = case turtle .# trace of
  [] -> turtle .# traces
  pts -> reversed (pts) : turtle .# traces

-------------------------------------------------------------------------------
-- Examples
-------------------------------------------------------------------------------

{-
turtleExample1 = (stars.#run.#tracks.#polylines & anchors.#polyline).#drawingOf
  where
  noStar(l) = run([pu,star(l),pd])
  star(l) = run([repeat(8,[fd(l/30),rt(135)]),pu,fd(3*l/30),rt(57),pd])
  tracedStar(l) = run([star(l),pu,fd(3*l/30),rt(57),pd])
  stars = foreach( [0,4..120], star)
  anchors = foreach([0,4..120], noStar).#run.#tracks.#concatenation
-}

randomExample = randomDrawingOf (draw)
  where
    draw (random) = polyline (track (prog (random)))
    prog (random) = run (foreach (first (random, 50), prog1))
    prog1 (r) = run ([prog2 (randomNumbers (r)), rt (180)])
    prog2 (random) = repeat (truncation (100 * random # 1), [fd (10), bk (9.8), rt (2)])

-- | The first example is based on the following code:
--
-- >   program = drawingOf(polylines(tracks(turtleProgram)))
-- >   turtleProgram = run(figs(14, poly(7,2)))
-- >   figs(n,fig) = [repeat(n,[run(fig),lt(360/n)])]
-- >   poly(n,len) = figs(n,[fd(len)])
--
-- Note how normal CodeWorld functions are integrated with the turtle
-- commands, even in the presence of recursion.
--
-- The rest of the examples are taken
-- from the Logo 15-word challenge at
-- <http://www.mathcats.com/gallery/15wordcontest.html>
--
-- The keywords before each name show information about how many 'repeat'
-- statements each example used, whether it used advanced control functions,
-- such as 'foreach' or 'forloop', and whether advanced math functions, such
-- as trig functions or logarithms were used to define the shape. You can
-- use this information to estimate the relative complexity of the shapes.
--
-- Here are the codes for the examples:
--
-- >   ring = let fd'(x) = fd(x/20)
-- >              bk'(x) = bk(x/20)
-- >          in sethome(4,0)
-- >             : [repeat(16, [ fd'(85), lt(60), fd'(107)
-- >                           , bk'(72), lt(53), fd'(74)])]
-- >
-- >   blade = let fd'(x) = fd(x/20)
-- >               bk'(x) = bk(x/20)
-- >           in sethome(-3,-4)
-- >              : [repeat(36,[fd'(60),rt(61),bk'(80),lt(41),fd'(85),rt(41)])]
-- >
-- >   hypercube = sethome(-3.5,1.5)
-- >     : [repeat(8,[repeat(4,[rt(90),fd(3)]),bk(3),lt(45)])]
-- >
-- >   star1 = sethome(-2.5,-3.5)
-- >     : [repeat(18,[repeat(5,[rt(40),fd(10),rt(120)]),rt(20)])]
-- >
-- >   fanflower = sethome(-1.5,5)
-- >               : [repeat(12,[repeat(75,[fd(4),bk(4),rt(2)]),fd(10)])]
-- >
-- >   jagged1 = sethome(0,-6)
-- >     : [repeat(4,[repeat(30,[lt(90),fd(0.2),rt(90),fd(0.2)]),rt(90)])]
-- >
-- >   jagged2 = sethome(4,-6)
-- >     : [repeat(4,[repeat(20,[lt(160),fd(1.5),rt(160),fd(1.5)]),rt(90)])]
-- >
-- >   jagged3 = sethome(2.5,-7) : lt(5)
-- >     : [repeat(8,[repeat(20,[lt(170),fd(1.5),rt(170),fd(1.5)]),rt(45)])]
-- >
-- >   pentahexa = sethome(-0.5,-2)
-- >     : [repeat(5,[repeat(6,[fd(4),lt(72)]),lt(144)])]
-- >
-- >   leaves(n) = -- Useful values for n: 1 to 7
-- >     sethome(-2.7,-1.2)
-- >     : [repeat(8,[rt(45),repeat(n,[repeat(90,[fd(0.1),rt(2)]),rt(90)])])]
-- >
-- >   roses(l,n,k) = foreach([1..360*n],\i -> run([fd(l/10),rt(i+x)]))
-- >     where
-- >     x = (2*k - n) / (2*n)
-- >     -- Useful values:
-- >     -- roses 5 5 3
-- >     -- roses 5 7 3
-- >     -- roses 5 10 7
-- >     -- roses 5 12 5
-- >
-- >   bullring = sethome(-3,-0.5)
-- >     : foreach([0..1002],\i -> run([fd(0.4),seth(360*i^3 / 1002)]))
-- >
-- >   squareSpiral = foreach([1..800],\i -> run([fd(i/40),rt(89)]))
-- >
-- >   diaphragm = foreach([1..100],\i -> let fd'(x) = fd(x/20)
-- >     in run([fd'(5+i),rt(45),fd'(10+i),rt(60)]))
-- >
-- >   octagons =
-- >     foreach( [1..15]
-- >        , \i -> repeat(5, [repeat(8, [fd (0.4 + i*0.2), rt(45)]), rt(72)])
-- >        )
-- >
-- >   circleSpiral = foreach([0,0.05..4],\i -> repeat(180,[fd(i/10),rt(1)]))
-- >
-- >   pentaStarSpiral =
-- >     foreach( [0,3..96]
-- >            , \l -> run([repeat(5,[fd(l/20),rt(144)]),fd(l/20),rt(30)]))
-- >
-- >   octaStarSpiral =
-- >     foreach( [0,4..120],
-- >        \l -> run([repeat(8,[fd(l/30),rt(135)]),pu,fd(2*l/30),rt(30),pd]))
-- >
-- >   sqcirc1 =
-- >     foreach( [1..36],
-- >        \i -> run([repeat(36,[fd(0.5),rt(10)]),fd(i/20),rt(90),fd(i/20)]))
-- >
-- >   pentapenta =
-- >     foreach([10,9..1],\i -> repeat(5,[repeat(5,[fd(i/2),lt(72)]),lt(72)]))
-- >
-- >   hexagon2 = foreach( [100,95..10]
-- >                 , \i -> repeat(6,[repeat(6,[fd(i/20),lt(60)]),lt(60)]))
-- >
-- >   hexagon1 = foreach( [100,50..50]
-- >                 , \i -> repeat(6,[repeat(6,[fd(i/20),lt(60)]),lt(60)]))
-- >
-- >   jaggystar = fd(-6.5)
-- >             : foreach([0..2200],\i -> run([fd(0.75*sin(i)), rt(i^2)]))
-- >
-- >   fish1 = foreach([1..360],\t -> run(cmds(t)))
-- >     where
-- >     cmds(t) = [ overxy(\(x,y) -> (8*cos(2*t),y))
-- >               , overxy(\(x,y) -> (x,x*cos(t)))
-- >               , home
-- >               ]
-- >
-- >   fish2 = foreach([-315..315],\t -> setxy(t*sin(t)/50,0.5*t*cos(2*t)/50))
-- >
-- >   gillyflower =
-- >     foreach( [1..450]
-- >            , \i -> let a = 73 * sin(i) in run([fd(a/20),rt(88*cos(a))]))
-- >
-- >   petals(n) = foreach([0..180],\t -> run([seth(t),fd(10*sin(t*n)),home]))
-- >
-- >   eye = foreach([1..1800],\i -> run([fd(log(i)/10),bk(sin(i)),rt(10)]))
-- >
-- >   spirotunnel =
-- >     forloop( 1,(<= 160),\i -> i + sin(i)
-- >            , \i -> run([fd(i/80),bk(i/10),rt(51)])
-- >            )
-- >
-- >   neutronStar =
-- >     forloop(1,(<= 4),\i -> i + sin(i+7)/2
-- >            ,\i -> let a = 2*i
-- >                   in run([fd(a),bk(a),rt(41)]))
-- >
turtleExamples :: Program
turtleExamples =
  (slides .$ make ++ [bullring .# run .# tracks .# dottylines]) .# slideshow
  where
    make (i, l, s) =
      blank
        & translated (lettering (i <> ": " <> l), 0, 9.5)
        & translated (colored (solidRectangle (10, 1.2), RGB (1, 1, 1)), 0, 9.5)
        & s .# run .# tracks .# polylines
    slides =
      [ ("recursive", "figs(14, poly(7,2))", figs (14, poly (7, 2))),
        ("rep 1", "ring", ring),
        ("rep 1", "blade", blade),
        ("rep 2", "hypercube", hypercube),
        ("rep 2", "star1", star1),
        ("rep 2", "fanflower", fanflower),
        ("rep 2", "jagged1", jagged1),
        ("rep 2", "jagged2", jagged2),
        ("rep 2", "jagged3", jagged3),
        ("rep 2", "pentahexa", pentahexa),
        ("rep 3", "leaves(3)", leaves (3)),
        ("rep 3", "leaves(7)", leaves (7)),
        ("foreach", "roses(5,5,3)", roses (5, 5, 3)),
        ("foreach", "roses(5,12,5)", roses (5, 12, 5)),
        ("foreach", "bullring", bullring),
        ("foreach", "squareSpiral", squareSpiral),
        ("foreach", "diaphragm", diaphragm),
        ("foreach+rep 1", "octagons", octagons),
        ("foreach+rep 1", "circleSpiral", circleSpiral),
        ("foreach+rep 1", "pentaStarSpiral", pentaStarSpiral),
        ("foreach+rep 1", "octaStarSpiral", octaStarSpiral),
        ("foreach+rep 1", "sqcirc1", sqcirc1),
        ("foreach+rep 2", "pentapenta", pentapenta),
        ("foreach+rep 2", "hexagon2", hexagon2),
        ("foreach+rep 2", "hexagon1", hexagon1),
        ("foreach+trig", "jaggystar", jaggystar),
        ("foreach+trig", "fish1", fish1),
        ("foreach+trig", "fish2", fish2),
        ("foreach+trig", "gillyflower", gillyflower),
        ("foreach+trig", "petals(7)", petals (7)),
        ("foreach+trig+log", "eye", eye),
        ("forloop+trig", "spirotunnel", spirotunnel),
        ("forloop+trig", "neutronStar", neutronStar)
      ]
    test = [repeat (8, [clover, rt (45)])]
      where
        n = 3
        base = repeat (90, [fd (0.1), rt (2)])
        clover = repeat (n, [base, rt (90)])
    figs (n, fig) = [repeat (n, [run (fig), lt (360 / n)])]
    poly (n, len) = figs (n, [fd (len)])
    ---
    --- Codes for examples
    ---

    ring =
      let fd' (x) = fd (x / 20)
          bk' (x) = bk (x / 20)
       in sethome (4, 0)
            : [repeat (16, [fd' (85), lt (60), fd' (107), bk' (72), lt (53), fd' (74)])]
    blade =
      let fd' (x) = fd (x / 20)
          bk' (x) = bk (x / 20)
       in sethome (-3, -4)
            : [repeat (36, [fd' (60), rt (61), bk' (80), lt (41), fd' (85), rt (41)])]
    hypercube =
      sethome (-3.5, 1.5)
        : [repeat (8, [repeat (4, [rt (90), fd (3)]), bk (3), lt (45)])]
    star1 =
      sethome (-2.5, -3.5)
        : [repeat (18, [repeat (5, [rt (40), fd (10), rt (120)]), rt (20)])]
    fanflower =
      sethome (-1.5, 5)
        : [repeat (12, [repeat (75, [fd (4), bk (4), rt (2)]), fd (10)])]
    jagged1 =
      sethome (0, -6)
        : [repeat (4, [repeat (30, [lt (90), fd (0.2), rt (90), fd (0.2)]), rt (90)])]
    jagged2 =
      sethome (4, -6)
        : [repeat (4, [repeat (20, [lt (160), fd (1.5), rt (160), fd (1.5)]), rt (90)])]
    jagged3 =
      sethome (2.5, -7) : lt (5)
        : [repeat (8, [repeat (20, [lt (170), fd (1.5), rt (170), fd (1.5)]), rt (45)])]
    pentahexa =
      sethome (-0.5, -2)
        : [repeat (5, [repeat (6, [fd (4), lt (72)]), lt (144)])]
    leaves (n) =
      -- Useful values for n: 1 to 7
      sethome (-2.7, -1.2)
        : [repeat (8, [rt (45), repeat (n, [repeat (90, [fd (0.1), rt (2)]), rt (90)])])]
    roses (l, n, k) = foreach ([1 .. 360 * n], \i -> run ([fd (l / 10), rt (i + x)]))
      where
        x = (2 * k - n) / (2 * n)
    -- Useful values:
    -- roses 5 5 3
    -- roses 5 7 3
    -- roses 5 10 7
    -- roses 5 12 5

    bullring =
      sethome (-3, -0.5)
        : foreach ([0 .. 1002], \i -> run ([fd (0.4), seth (360 * i ^ 3 / 1002)]))
    squareSpiral = foreach ([1 .. 800], \i -> run ([fd (i / 40), rt (89)]))
    diaphragm =
      foreach
        ( [1 .. 100],
          \i ->
            let fd' (x) = fd (x / 20)
             in run ([fd' (5 + i), rt (45), fd' (10 + i), rt (60)])
        )
    octagons =
      foreach
        ( [1 .. 15],
          \i -> repeat (5, [repeat (8, [fd (0.4 + i * 0.2), rt (45)]), rt (72)])
        )
    circleSpiral = foreach ([0, 0.05 .. 4], \i -> repeat (180, [fd (i / 10), rt (1)]))
    pentaStarSpiral =
      foreach
        ( [0, 3 .. 96],
          \l -> run ([repeat (5, [fd (l / 20), rt (144)]), fd (l / 20), rt (30)])
        )
    octaStarSpiral =
      foreach
        ( [0, 4 .. 120],
          \l -> run ([repeat (8, [fd (l / 30), rt (135)]), pu, fd (2 * l / 30), rt (30), pd])
        )
    sqcirc1 =
      foreach
        ( [1 .. 36],
          \i -> run ([repeat (36, [fd (0.5), rt (10)]), fd (i / 20), rt (90), fd (i / 20)])
        )
    pentapenta =
      foreach ([10, 9 .. 1], \i -> repeat (5, [repeat (5, [fd (i / 2), lt (72)]), lt (72)]))
    hexagon2 =
      foreach
        ( [100, 95 .. 10],
          \i -> repeat (6, [repeat (6, [fd (i / 20), lt (60)]), lt (60)])
        )
    hexagon1 =
      foreach
        ( [100, 50 .. 50],
          \i -> repeat (6, [repeat (6, [fd (i / 20), lt (60)]), lt (60)])
        )
    jaggystar =
      fd (-6.5)
        : foreach ([0 .. 2200], \i -> run ([fd (0.75 * sin (i)), rt (i ^ 2)]))
    fish1 = foreach ([1 .. 360], \t -> run (cmds (t)))
      where
        cmds (t) =
          [ overxy (\(x, y) -> (8 * cos (2 * t), y)),
            overxy (\(x, y) -> (x, x * cos (t))),
            home
          ]
    fish2 = foreach ([-315 .. 315], \t -> setxy (t * sin (t) / 50, 0.5 * t * cos (2 * t) / 50))
    gillyflower =
      foreach
        ( [1 .. 450],
          \i -> let a = 73 * sin (i) in run ([fd (a / 20), rt (88 * cos (a))])
        )
    petals (n) = foreach ([0 .. 180], \t -> run ([seth (t), fd (10 * sin (t * n)), home]))
    eye = foreach ([1 .. 1800], \i -> run ([fd (log (i) / 10), bk (sin (i)), rt (10)]))
    spirotunnel =
      forloop
        ( 1,
          (<= 160),
          \i -> i + sin (i),
          \i -> run ([fd (i / 80), bk (i / 10), rt (51)])
        )
    neutronStar =
      forloop
        ( 1,
          (<= 4),
          \i -> i + sin (i + 7) / 2,
          \i ->
            let a = 2 * i
             in run ([fd (a), bk (a), rt (41)])
        )
